package thread.test1Io;

import org.junit.Test;

import java.io.*;
import java.security.DigestInputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Date;
import java.util.Enumeration;
import java.util.Vector;

/**
 * Created by majian8 on 2019/6/9.
 *
 * 所有流的read方法当无可用的数据时会返回 -1 当文件本身的长度就是0的话，会返回0？（待验证）
 *
 * https://blog.csdn.net/jiangwei0910410003/article/details/22376895
 */
public class InputStreamTest {
    /**
     * io的mark操作就是标记一个位置，然后reset到标记的位置重新读取 可以看出 .116.104. 105.115.32.
     * 105.115.32. 97.32.119.111.114.100 其中105.115.32读了两遍，
     *
     * 这个标记只对 BufferedInputStream 和byteArrayInputstream 有作用
     * （如果超过了BufferedInputStream 的缓冲区就无作用了？）
     * @throws IOException
     */
    @Test
    public void test1() throws IOException {

        InputStream in = new BufferedInputStream(new FileInputStream(new File(
                "d:/test.txt")));
        // 不是所有的流都支持mark操作，所以要判断流是不是支持mark
        if (!in.markSupported()) {
            System.out.println("mark/rset is not supported");
            return;
        }

        int ch;
        int count = 0;
        boolean marked = false;
        while ((ch = in.read()) != -1) {
            System.out.println("." + ch);

            if ((ch == 4) && !marked) {
                // 在4的位置标记
                in.mark(10);
                marked = true;
            }
            if (ch == 8 && count < 2) {
                // 重设位置到4
                in.reset();
                count++;
            }
        }

        in.close();
    }

    /**
     * ByteArrayOutputStream与 ByteArrayInputStream
     *
     * ByteArrayOutputStream类是在创建它的实例时，程序内部创建一个byte型别数组的缓冲区，
     * 然后利用ByteArrayOutputStream和ByteArrayInputStream的实例向数组中写入或读出byte型数据。
     * 在网络传输中我们往往要传输很多变量，我们可以利用ByteArrayOutputStream把所有的变量收集到一起，然后一次性把数据发送出去。
     *
     *
     * */
    @Test
    public void ByteArrayOutputStreamTest() throws IOException {
        // 向内存中写数据，写个大文件
        FileInputStream fis = new FileInputStream(new File(
                "d:/a.txt"));
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        int len = 0;
        byte[] bt = new byte[1024];
        while ((len = fis.read(bt)) != -1) {
            bos.write(bt);
        }
        System.out.println();
        // 从内存中读出
        ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
        byte[] bt2 = new byte[1024];
        while ((bis.read(bt2) != -1)) {
            System.out.println(Arrays.toString(bt2));
        }
        // in关闭
        bis.close();
        // out关闭
        fis.close();
        bos.close();
        System.out.println();

    }

    /**
     * StringBufferInputStream -- 把一个 String 对象作为 InputStream 不推荐使用
     * StringBufferInputStream 方法。 此类不能将字符正确的转换为字节。 同 JDK 1.1
     * 版中的类似，从一个串创建一个流的最佳方法是采用 StringReader 类
     *
     * @throws IOException
     * */
    @Test
    public void test3() throws IOException {
        String str = "American navy";
        InputStream in = new StringBufferInputStream(str);
        byte[] bt = new byte[1];
        int len = 0;
        while ((len = in.read(bt)) != -1) {
            System.out.println((char) bt[0]);
        }
        in.close();
    }

    /**
     * ObjectInputStream,用在序列化和反序列化中， object要实现Serializable 接口
     * 对于不想序列化的属性，要加transient,对于static也 不会序列化
     *
     * @throws IOException
     * @throws ClassNotFoundException
     */
    @Test
    public void test4() throws IOException, ClassNotFoundException {
        User user1 = new User("zhangsan", 20, new Date(), "xuliehua");
        User user2 = new User("lisi", 30, new Date(), "xuliehua");
        FileOutputStream fos = new FileOutputStream("d:/a.txt");
        // 对象序列化
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(user1);
        oos.writeObject(user2);
        oos.close();
        // 对象反序列化
        FileInputStream fis = new FileInputStream(new File("d:/a.txt"));
        ObjectInputStream ois = new ObjectInputStream(fis);
        User user = (User) ois.readObject();
        System.out.println(user.getName());
        ois.close();
    }

    /** ################################################################# */
    /**
     * FileInputStream -- 把一个文件作为 InputStream ，实现对文件的读取操作 Java
     * I/O默认是不缓冲流的，所谓“缓冲”就是先把从流中得到的一块字节序列暂存在一个被称为buffer的内部字节数组里
     * ，然后你可以一下子取到这一整块的字节数据，没有缓冲的流只能一个字节一个字节读，效率孰高孰低一目了然。
     * 有两个特殊的输入流实现了缓冲功能，一个是我们常用的BufferedInputStream
     *
     * @throws IOException
     * */

    // 单个读取，有多少个字节读多少次，len为读取的内容 转化为char型
    @Test
    public void test5() throws IOException {
        FileInputStream fis = new FileInputStream(new File("d:/a.txt"));
        int len = 0;
        while ((len = fis.read()) != -1) {
            System.out.println(len);
        }
        fis.close();
    }

    /**
     *当运动缓冲流的时候要flush ，否则可能 由于缓冲没填满，而得不到输出。
     */
    @Test
    public void test55() throws IOException {
        FileInputStream fis = new FileInputStream(new File("d:/a.txt"));
        FileOutputStream fos=new FileOutputStream(new File("d:/aa.txt"));
        BufferedOutputStream bos=new BufferedOutputStream(fos);

        bos.flush();
        fis.close();
    }

    // 每次读取指定长度的的数组，末尾没有被0填充
    @Test
    public void test6() throws IOException {
        FileInputStream fis = new FileInputStream(new File("d:/a.txt"));
        int len = 0;
        byte[] bt = new byte[3];
        while ((len = fis.read(bt)) != -1) {
            System.out.println(new String (bt,"utf-8"));
        }
        fis.close();

    }

    // 如上面我们预先申请了一个指定大小的空间，但是有时候这个空间可能太小，有时候可能太大，
    // 我们需要准确的大小，这样节省空间，那么我们可以这样做
    @Test
    public void test7() throws IOException {
        File file = new File("d:/a.txt");
        FileInputStream fis = new FileInputStream(file);
        int len = 0;
        byte[] bt = new byte[(int) file.length()];
        fis.read(bt);
        System.out.println(new String(bt,"gbk"));
        fis.close();
    }

    // 通过BufferedInputStream读取，和test6代码差别很小（其默认的缓冲区越大，效果越明显？）
    // 其实BufferedInputStream的缓冲大于 byte[] bt=new byte[]时，才有效果。
    @Test
    public void test8() throws IOException {
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream(
                new File("d:/a.txt")));
        byte[] bt = new byte[1024];
        int len = 0;
        while ((len = bis.read(bt)) != -1) {
            System.out.println(Arrays.toString(bt));
        }
        bis.close();

    }

    /** ################################################################ */
    /**
     * SequenceInputStream 把多个 InputStream 合并为一个 InputStream .
     * “序列输入流”类允许应用程序把几个输入流连续地合并起来， 并且使它们像单个输入流一样出现。每个输入流依次被读取，直到到达该流的末尾。
     * 然后“序列输入流”类关闭这个流并自动地切换到下一个输入流。
     *
     * @throws IOException
     * */
    @Test
    public void test9() throws IOException {
        // 首先创建一个输入流的枚举
        Vector<FileInputStream> vector = new Vector<>();
        vector.add(new FileInputStream(new File("d:/a.txt")));
        vector.add(new FileInputStream(new File("d:/b.txt")));
        Enumeration<FileInputStream> en = vector.elements();
        // 创建序列输入流
        SequenceInputStream sq = new SequenceInputStream(en);
        // 包装成缓冲输入流
        BufferedInputStream bis = new BufferedInputStream(sq);
        int len = 0;
        byte[] bt = new byte[1024];
        while ((len = bis.read(bt)) != -1) {
            System.out.println(Arrays.toString(bt));
        }

    }

    /**
     * PipedInputStream
     * 管道流，用于线程间的通信。一个线程的PipedInputStream对象从另外一个线程的PipedOutputStream对象读取输入。
     * 要使管道流有用，必须同时构造管道输入流和管道输出流。
     * 先构建两个线程类PipedInThread和PipedOutThread(一个输出，一个输入)
     *
     * @throws IOException
     * */
    @Test
    public void test10() throws IOException {
        PipedOutputStream pos = new PipedOutputStream();
        PipedInputStream pis = new PipedInputStream();
        pos.connect(pis);// 连接管道
        new PipedOutThread(pos).start();// 启动线程
        new PipedInThread(pis).start();
    }

    /** ############################################################# */
    /** 包装流FilterInputStream的各个实现类 */

    /**
     * BufferedInputStream 内部有一个缓冲区，默认大小为8M，每次调用read方法的时候，它首先尝试从缓冲区里读取数据，
     * 若读取失败（缓冲区无可读数据），则选择从物理数据源（譬如文件）读取新数据（这里会尝试尽可能读取多的字节）放入到缓冲区中，
     * 最后再将缓冲区中的内容部分或全部返回给用户.由于从缓冲区里读取数据远比直接从物理数据源（譬如文件）读取速度快，
     * 所以BufferedInputStream的效率很高！
     *
     * @throws IOException
     */
    @Test
    public void test11() throws IOException {
        ByteArrayInputStream bin = new ByteArrayInputStream(
                "hello world".getBytes());
        BufferedInputStream bis = new BufferedInputStream(bin);
        int len = 0;
        byte[] bt = new byte[1024];
        while ((len = bis.read(bt)) != -1) {
            System.out.println(Arrays.toString(bt));
        }
    }

    /** ############过滤流################################ */
    /**
     * digestOutputStream
     *
     * @description
     * @version currentVersion
     * @author mj
     * @throws NoSuchAlgorithmException
     * @createtime 2017年1月7日 下午10:14:01
     */
    @Test
    public void test42() throws NoSuchAlgorithmException {
        File file=new File("d:/a.txt");
        try {
            FileInputStream fin=new FileInputStream(file);
            MessageDigest sha=MessageDigest.getInstance("sha-256");
            DigestInputStream din=new DigestInputStream(fin, sha);
           // System.out.println(DatatypeConverter.printHexBinary(sha.digest()));
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }

    }
}
